package com.gvsoft;


import com.gvsoft.Client;
import com.gvsoft.analyse.inf.IClientOrder;
import com.gvsoft.analyse.order.ClientOrder;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Map;
import org.apache.logging.log4j.LogManager;

/**
 * Created with IntelliJ IDEA.
 * User: zhaoqiubo
 * Date: 15/7/27
 * Time: 下午3:55
 * To change this template use File | Settings | File Templates.
 */
public class GVClientMThread extends Thread {

    protected static SocketChannel socketChannel = null;
    private final static int BLOCK = 500;
    protected static Selector selector = null;
    private static final int PACKET_HEAD_LENGTH = 4;
    //断包处理，前一截包含完整包头；
    private String endPacketStr = "";
    //断包处理，前一截不包含完整包头；
    private String endBufferStr = "";
    //断包处理，前一截包含完整包头时，包体的大小标记；
    private int remainBodySize = 0;

    private static Logger logger = LogManager.getLogger(GVClientMThread.class.getName());

    public void run() {
        logger.info("服务端报文读取主线程启动……");

            while (true) {
                try {

                    selector.select(3000);

                    Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                    for (int i = 0; keys.hasNext(); i++) {

                        SelectionKey key = keys.next();
                        keys.remove();
                        if (key.isConnectable()) {
                            socketChannel = (SocketChannel) key.channel();
                            if (socketChannel.isConnectionPending()) {
                                if (socketChannel.finishConnect()){
                                    Client.IS_CONNECT =true;
                                    logger.info("-------成功连接服务端！-------");
                                }

                            }
                            socketChannel.register(selector, SelectionKey.OP_READ);
                        } else if (key.isReadable()) {
                            //获取事件key中的channel
                            socketChannel = (SocketChannel) key.channel();
                            ByteBuffer byteBuffer = ByteBuffer.allocate(BLOCK);
                            //清理缓冲区，便于使用
                            byteBuffer.clear();
                            //将channel中的字节流读入缓冲区
                            String readStr = "";
                            int count = socketChannel.read(byteBuffer);
                            //务必要把buffer的position重置为0
                            byteBuffer.flip();

                            handlePacket(byteBuffer, count);
//                            socketChannel.register(selector, SelectionKey.OP_READ);
                        } else if (key.isWritable()) {
                            socketChannel.register(selector, SelectionKey.OP_READ);
                        }

                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    continue;
                }

            }

    }

    private void handleRead(ByteBuffer byteBuffer, int count) throws IOException {
        if (count > 0) {
            //标记读取缓冲区起始位置
            int location = 0;
            //如果缓冲区从0到limit的数量大于包体大小标记数字
            while (byteBuffer.remaining() > PACKET_HEAD_LENGTH) {

                String strBsize;//包体大小标记
                strBsize = new String(byteBuffer.array(), location, PACKET_HEAD_LENGTH);
                byteBuffer.position(location + PACKET_HEAD_LENGTH);//移动缓冲区position
//                Logs.info("收到服务端包体大小：" + strBsize + "，查看position变化：" + byteBuffer.position());

                int byteBufferSize = Integer.parseInt(strBsize.trim());//得到包体大小

                if (byteBuffer.remaining() >= byteBufferSize) {//如果从缓冲区当前位置到limit大于包体大小，进行包体处理

                    String strPacket = new String(byteBuffer.array(), PACKET_HEAD_LENGTH + location, byteBufferSize);
                    byteBuffer.position(location + PACKET_HEAD_LENGTH + byteBufferSize);//将缓冲区的位置移动到下一个包体大小标记位置
//                    Logs.info("收到服务端包体内容：" + strPacket + "，查看position变化：" + byteBuffer.position());

                    offerPacket(strPacket);
                    location = location + PACKET_HEAD_LENGTH + byteBufferSize;//设定读取缓冲区起始位置
                }
            }
            //我也不知道这是否有用，
            byteBuffer.clear();
        } else if (count == 0) {
            return;
        } else {
            socketChannel.close();
            Client.clearToken4Disconnect();
//            this.interrupt();
        }
    }

    private void handlePacket(ByteBuffer byteBuffer, int count) throws IOException {
        if (count > 0) {
            //标记读取缓冲区起始位置
            int location = 0;
            //如果缓冲区从0到limit的数量大于包体大小标记数字
            while (byteBuffer.remaining() > PACKET_HEAD_LENGTH) {
                //包体大小标记
                String strBsize;
                //如果endPacket的字节length大于0，则证明：断包的前一截为包含包头和包体的；
                if (endPacketStr.getBytes().length > 0) {

                    String strPacket = endPacketStr.substring(PACKET_HEAD_LENGTH) + new String(byteBuffer.array(), 0, remainBodySize);
                    byteBuffer.position(remainBodySize);
                    location = remainBodySize;
//                                    if(logger.isDebugEnabled()) {
                    logger.info("【断包处理】(包含包体)合并后的报文:" + strPacket + "，缓冲区的position:" + location);
//                                    }
                    offerPacket(strPacket);
                    //处理完毕，清理断包的前一截，以便于下次使用；
                    endPacketStr = "";
                    //清理后一截报文的字节数标记；
                    remainBodySize = 0;
                    continue;
                    //如果endBufferStr的字节length大于0，则证明：断包的前一截仅包含包头或包头的一部分，不包含包体；
                } else if (endBufferStr.getBytes().length > 0) {

                    strBsize = (new StringBuffer(endBufferStr).append(new
                            String(byteBuffer.array(), location, PACKET_HEAD_LENGTH - endBufferStr.getBytes().length))).toString();

                    //移动缓冲区position
                    byteBuffer.position(PACKET_HEAD_LENGTH - endBufferStr.getBytes().length);
                    location = byteBuffer.position();
                    //得到包体大小
                    int byteBufferSize = Integer.parseInt(strBsize.trim());
                    //进行报文合并，把保存的仅包含包头或包头一部分的前一截与后一截合并
                    String strPacket = endBufferStr + (new String(byteBuffer.array(), PACKET_HEAD_LENGTH - endBufferStr.getBytes().length, byteBufferSize));
                    byteBuffer.position(location + byteBufferSize);//将缓冲区的位置移动到下一个包体大小标记位置
                    location = byteBuffer.position();

                    logger.info("【断包处理】(不包含包体)合并后的报文:" + strPacket + "，缓冲区的position:" + location);
                    offerPacket(strPacket);
                    endBufferStr = "";
                    continue;
                    //进入正常处理（规范的报文处理，不考虑断包）
                } else {
                    strBsize = new String(byteBuffer.array(), location, PACKET_HEAD_LENGTH);
                    //移动缓冲区position
                    byteBuffer.position(location + PACKET_HEAD_LENGTH);
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("收到服务端包体大小：" + strBsize + "，查看position变化：" + byteBuffer.position());
                }
                //得到包体大小
                int byteBufferSize = Integer.parseInt(strBsize.trim());
                //如果从缓冲区当前位置到limit大于包体大小，证明粘包了，进行包体处理。等于则为正常包体，不存在粘包现象。
                if (byteBuffer.remaining() >= byteBufferSize) {

                    String strPacket = endBufferStr + (new String(byteBuffer.array(), PACKET_HEAD_LENGTH + location, byteBufferSize));
                    byteBuffer.position(location + PACKET_HEAD_LENGTH + byteBufferSize);//将缓冲区的位置移动到下一个包体大小标记位置
                    if (logger.isDebugEnabled()) {
                        logger.debug("收到服务端包体内容：" + strPacket + "，2查看position变化：" + byteBuffer.position());
                    }
                    offerPacket(strPacket);
                    location = byteBuffer.position();//设定读取缓冲区起始位置
                    //如果缓冲区当前位置到limit小于包体，证明断包了,进行断包处理
                } else {

                    endPacketStr = new String(byteBuffer.array(), location, byteBuffer.limit() - location);
                    remainBodySize = Integer.parseInt(endPacketStr.substring(0, PACKET_HEAD_LENGTH).trim()) - endPacketStr.getBytes().length + PACKET_HEAD_LENGTH;
                    //已经找到断包前半截，所以把整个buffer的position调整至最后，不再处理。等待新的key进入
                    byteBuffer.position(byteBuffer.limit());
                    logger.info("处理断包仅包含完整包头的尾部报文，缓冲区位置:" + location + "，缓冲区limit:" + byteBuffer.limit() + "，包含完全包头的剩余字符:" + endPacketStr + "，bodySize:" + remainBodySize);

                }
            }
            //处理仅包含包头前一截的报文；
            if (byteBuffer.remaining() > 0) {

                //缓冲区中剩余的仅包含包头前一截的报文
                endBufferStr = new String(byteBuffer.array(), location, byteBuffer.limit() - location);

                logger.info("处理断包仅包含包头前一截的尾部报文，缓冲区位置:" + location + "，缓冲区limit:" + byteBuffer.limit() + "，不包含完全包头的剩余字符：" + endBufferStr);
                //移动缓冲区指针到最后，代表已经保存了前一截报文，无需再进行处理；
                byteBuffer.position(byteBuffer.limit());
            }
            //我也不知道这是否有用，能不能释放内存资源
            byteBuffer.clear();
        } else if (count == 0) {
            return;
        } else {
            socketChannel.close();
            Client.clearToken4Disconnect();
//            this.interrupt();
        }
    }

    private void offerPacket(String strPacket) {
        if (Client.MSGQUEUE.offer(strPacket.trim())) {
            if(logger.isDebugEnabled()) {
                logger.debug("将添加至消息队列，队列内消息数量为：" + Client.MSGQUEUE.size());
            }
        } else {
            logger.error("【严重错误】消息队列已满，消息处理已经遭遇性能瓶颈，需尽快处理！！！");
        }
    }

    /**
     * 向通道写入信息的方法
     *
     * @param clientOrder   报文处理类接口
     * @param socketChannel
     */
    protected static void write2Channel(IClientOrder clientOrder, SocketChannel socketChannel) throws IOException{
        try {
            socketChannel.register(selector, SelectionKey.OP_WRITE);
            //创建一个byteBuffer用来存储要写入的buffer
            ByteBuffer byteBuffer = ByteBuffer.allocate(BLOCK);
            //得出整个包体的长度
            String packetSize = Integer.toString(clientOrder.generateOrderStr().getBytes().length);
            //讲包体长度放入buffer的前四位
            byteBuffer.put(packetSize.getBytes());
            //移动buffer的postion指针到第四位，包体将从第四位开始写入
            byteBuffer.position(PACKET_HEAD_LENGTH);
            String str = clientOrder.generateOrderStr();
            //写入包体
            logger.info("客户端写入通道的包体：" + str+",length:"+str.getBytes().length);
            byteBuffer.put(str.getBytes());
            byteBuffer.flip();
            socketChannel.write(byteBuffer);
        } catch (IOException e) {
            throw e;
        }
    }


}
